Conditional Typesを末尾再帰で書く
ここでの「最適化」は、ネストに対する許容と捉えるのが良い
何が嬉しいか?
TypeScriptの型は再帰が深すぎると、型エラーが生じる
ここで、末尾再帰で書くようにすると、それがかなり許容される
つまり、自分で再帰型を定義する際に、末尾再帰になるように心がければ良い
例
引数にとった文字列を、1文字ずつ要素にした配列型に変換する型を考える
末尾再帰でない書き方で書くと以下のようになる
code:ts
type Split<S>
= S extends ${infer Char}${infer Rest}
: [];
全く同じ挙動をするものを末尾再帰で書くと以下のようになる
code:ts
type SplitTR<S extends string> = Split_<S, []>;
type Split_<S extends string, RS extends string[]>
= S extends ${infer H}${infer Tail}
: RS;
補助型関数Split_を導入し、これが末尾再帰になっている これら2つの型に長さ50の文字列を与える
code:4.5.ts
type R1 = SplitTR<'01234567890123456789012345678901234567890123456789'>; // ok
type R2 = Split<'01234567890123456789012345678901234567890123456789'>; // ng
code:4.4.ts
type R1 = SplitTR<'01234567890123456789012345678901234567890123456789'>; // ng
type R2 = Split<'01234567890123456789012345678901234567890123456789'>; // ng
v4.5では、末尾再帰にした時にのみerrorが出ない
v4.4では、いずれの場合もerrorが出る
具体的な数字にどれほど意味があるのかはわからないが、数えてみると以下の感じだった
v4.5の末尾再帰は、1000文字までok
v4.5の末尾再帰でないものは、48文字までok
結果がunionな末尾再帰は、最適化の恩恵が受けられない
これはerrorになる
code:ts
type GetChars<S>
= S extends ${infer Char}${infer Rest}
? Char | GetChars<Rest>
: never;
たしかに、これも一種の末尾再帰と言えるmrsekut.icon
以下のように書き直せば良い
code:ts
type GetChars<S> = GetCharsHelper<S, never>;
type GetCharsHelper<S, Acc>
= S extends ${infer Char}${infer Rest}
? GetCharsHelper<Rest, Char | Acc>
: Acc;
Haskellでも頻出するし、末尾再帰あるあるだと思うけど、補助関数を使えば末尾再帰は作りやすいmrsekut.icon